// Copyright (c)  YISH. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Text;
using NSQL.DateFormats;
using NSQL.Expressions;
using NSQL.Expressions.Functions;

namespace NSQL.Generator
{
    public class SqlGenerator : BaseGenerator, IExpressionVisitor<StringBuilder>
    {
        public static SqlGeneratorSettings DefaultSettings => new SqlGeneratorSettings()
        {
            Optimized = false,
            DatetimeFormatter = StandardDateFormatter.Instance
        };

        public static readonly ISqlGenerator Instance = new SqlGenerator(DefaultSettings);

        private readonly NSQLKeyWords _;

        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICastFunction castFunction)
        {
            var builder = new StringBuilder();
            builder.Append(castFunction.Expr.Accept(this));
            builder.Append("::");
            builder.Append(castFunction.TargetType);
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IDecodeFunction expression)
        {
            var builder = new StringBuilder(_.Decode);
            builder.Append('(').Append(expression.Items.Accept(this)).Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IMedianFunction expression)
        {
            var builder = new StringBuilder();
            builder.Append(_.Median);
            builder.Append('(').Append(expression.Expr.Accept(this)).Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IPercentileFunction expression)
        {
            var builder = new StringBuilder();
            switch (expression.Type)
            {
                case PercentileType.Cont:
                    builder.Append(_.Percentile);
                    break;
                case PercentileType.Disc:
                    builder.Append(_.Percentile).Append('_').Append(_.Disc);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('(');
            builder.Append(expression.Expr.Accept(this));

            builder.Append(", ").Append(expression.P.Accept(this));

            switch (expression.Order)
            {
                case Order.None:
                    break;
                case Order.Ascending:
                    builder.Append(", ").Append(_.Asc);
                    break;
                case Order.Descending:
                    builder.Append(", ").Append(_.Desc);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(')');

            return builder;
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ITrimFunction trimFunction)
        {
            var  builder =  new StringBuilder();
            builder.Append(_.Trim);
            switch (trimFunction.Type)
            {
                case TrimType.Both:
                    break;
                case TrimType.Leading:
                    builder.Append('_').Append(_.Start);
                    break;
                case TrimType.Trailing:
                    builder.Append('_').Append(_.End);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('(');
            builder.Append(trimFunction.Expr.Accept(this));
            if (!(trimFunction.TrimExpr is null))
            {
                builder.Append(", ");
                builder.Append(trimFunction.TrimExpr?.Accept(this));
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAddIntervalFunction addIntervalFunction)
        {
            var builder = new StringBuilder();
            switch (addIntervalFunction.Interval.Type)
            {
                case IntervalType.Years:
                    builder.Append(_.Year);
                    break;
                case IntervalType.Months:
                    builder.Append(_.Month);
                    break;
                case IntervalType.Days:
                    builder.Append(_.Day);
                    break;
                case IntervalType.Hours:
                    builder.Append(_.Hour);
                    break;
                case IntervalType.Minutes:
                    builder.Append(_.Minute);
                    break;
                case IntervalType.Seconds:
                    builder.Append(_.Second);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('_');

            builder.Append(addIntervalFunction.Interval.Interval > 0 ? _.Add : _.Sub);

            builder.Append('(');
            builder.Append(addIntervalFunction.DateTime.Accept(this));
            builder.Append(", ").Append(Math.Abs(addIntervalFunction.Interval.Interval));
            builder.Append(')');
            return builder;
        }


        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IExtractFunction extractFunction)
        {
            var builder = new StringBuilder();
            switch (extractFunction.Type)
            {
                case ExtractType.Year:
                    builder.Append(_.Year);
                    break;
                case ExtractType.Month:
                    builder.Append(_.Month);
                    break;
                case ExtractType.Day:
                    builder.Append(_.Day);
                    break;
                case ExtractType.Hour:
                    builder.Append(_.Hour);
                    break;
                case ExtractType.Minute:
                    builder.Append(_.Minute);
                    break;
                case ExtractType.Second:
                    builder.Append(_.Second);
                    break;
                case ExtractType.Week:
                    builder.Append(_.Week);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return builder.Append('(').Append(extractFunction.DateTime.Accept(this)).Append(')');
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IDiffFunction expression)
        {
            var builder = new StringBuilder();
            switch (expression.Type)
            {
                case ExtractType.Year:
                    builder.Append(_.Year);
                    break;
                case ExtractType.Month:
                    builder.Append(_.Month);
                    break;
                case ExtractType.Week:
                    builder.Append(_.Week);
                    break;
                case ExtractType.Day:
                    builder.Append(_.Day);
                    break;
                case ExtractType.Hour:
                    builder.Append(_.Hour);
                    break;
                case ExtractType.Minute:
                    builder.Append(_.Minute);
                    break;
                case ExtractType.Second:
                    builder.Append(_.Second);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('_').Append(_.Diff);
            builder.Append('(').Append(expression.Start.Accept(this));
            builder.Append(", ").Append(expression.End.Accept(this)).Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IRankFunction expression)
        {
            var builder = new StringBuilder();
            if (expression.IsDense)
            {
                builder.Append(_.Dense).Append('_');
            }
            builder.Append(_.Rank).Append('(');
            
            builder.Append(expression.Expr.Accept(this));
            
            switch (expression.Order)
            {
                case Order.None:
                    break;
                case Order.Ascending:
                    builder.Append(", ").Append(_.Asc);
                    break;
                case Order.Descending:
                    builder.Append(", ").Append(_.Desc);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(')');

            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IStartWithFunction startWithFunction)
        {
            var builder = new StringBuilder();
            builder.Append(_.Start).Append('_').Append(_.With);
            builder.Append('(');
            builder.Append(startWithFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(startWithFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IEndWithFunction endWithFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(_.End).Append('_').Append(_.With);
            builder.Append('(');
            builder.Append(endWithFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(endWithFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IContainsFunction containsFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(_.Contains);
            builder.Append('(');
            builder.Append(containsFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(containsFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IFormatFunction expression)
        {
            var builder = new StringBuilder();

            if (expression.IsDateFormat)
            {
                builder.Append(_.Format);
                builder.Append('(');
                builder.Append(expression.Items[0].Accept(this));
                builder.Append(", ");
                var f = (StringExpression) Settings.DatetimeFormatter.Format(expression.Format.Value);
                builder.Append(f.Accept(this));
                builder.Append(')');
            }
            else
            {
                throw new NotSupportedException();
            }
            return builder;
        }

        protected override StringBuilder WrapIdentifier(StringBuilder builder, Identifier identifier, bool delimited = false)
        {
            return identifier == "*" ? builder.Append(identifier) : builder.Append('[').Append(identifier).Append(']');
        }
        public SqlGenerator(Func<SqlGeneratorSettings, SqlGeneratorSettings> setupAction)
            : this(setupAction(DefaultSettings))
        {
        }
        
        public SqlGenerator(SqlGeneratorSettings settings) : this(settings, new NSQLKeyWords(settings.LetterCase))
        {
        }
        
        private SqlGenerator(SqlGeneratorSettings settings, NSQLKeyWords keyWords) : base(settings, keyWords)
        {
            _ = keyWords;
        }
        
        protected class NSQLKeyWords : KeyWords
        {
            public NSQLKeyWords(LetterCase letterCase) : base(letterCase)
            {
            }
            
            public virtual string Contains => FormatLetter(nameof(Contains));
            public string Decode => FormatLetter(nameof(Decode));
            public virtual string Sub => FormatLetter(nameof(Sub));
            public override string Power => FormatLetter("Pow");
        }
    }
}